// Copyright (c) 2010 Omer Goshen

#include "GenericCamera.h"

using namespace Ogre;

GenericCamera::~GenericCamera()
{
	Root::getSingleton().removeFrameListener(this);
	//	delete mDebugNode;
//	mDebugNode = 0;
}

GenericCamera::GenericCamera(SceneManager* sceneMgr) :
	SceneNode(sceneMgr),
	mDebugNode(0),
	mId(howMany()),
	mSceneMgr(sceneMgr),
	mEntity(0),
	mCamera(0),
	mOrthoZoom(1)
{
	//GenericCamera::CamerasById.insert(std::pair<id, this>);

//	onCamAttached.connect(boost::bind(&GenericCamera::setDebugNodeVisible, this, false));
//	onCamDetached.connect(boost::bind(&GenericCamera::setDebugNodeVisible, this, true));

//	onCamDetached.connect(boost::bind(&GenericCamera::saveCamPos, this));
//	onCamDetached.connect(boost::bind(&GenericCamera::clearCamPtr, this));
//
//	draw();
}

void GenericCamera::draw()
{

	ColourValue col = ColourValue(1,1,1,.3);
	Ogre::String idStr = StringConverter::toString(howMany());

//	mDebugNode = createChildSceneNode(getName()+"_DebugNode"+idStr);
	mDebugNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(getName()+"_DebugNode"+idStr);

	ProceduralPrimitiveFactory ppf(mSceneMgr);
	Ogre::String entName = "CameraEntity"+idStr;
	ppf.createSphere(entName, "VertexColour/Flat", col, .1, 16, 16, true, 1, 1, 1);

	mEntity = mSceneMgr->createEntity(entName);
	mEntity->setCastShadows(false);

	mMaterial = Ogre::MaterialManager::getSingleton().create(getName() + "_Material", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
	mMaterial->setReceiveShadows(false);

	mMaterial->getTechnique(0)->setSchemeName("GBuffer");
	mMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false);
	mMaterial->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA);
	mMaterial->getTechnique(0)->getPass(0)->setDiffuse(col);

	mMaterial->createTechnique();
	mMaterial->getTechnique(1)->setSchemeName("NoGBuffer");
	mMaterial->getTechnique(1)->createPass();
	mMaterial->getTechnique(1)->getPass(0)->setLightingEnabled(false);
	mMaterial->getTechnique(1)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA);
	mMaterial->getTechnique(1)->getPass(0)->setDiffuse(col);

	mDebugNode->attachObject(mEntity);


	Vector3 dir = Vector3::NEGATIVE_UNIT_Z;
	Vector3 n = dir*1000;
	Real z = -1.75f;

	ManualObject* mo =  mSceneMgr->createManualObject("manual"+idStr);
//	SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode("manual_node"+mId);
	mo->setCastShadows(false);

	mo->begin(getName() + "_Material", Ogre::RenderOperation::OT_LINE_LIST);

	mo->position(0,0,0);
	mo->colour(col);

	mo->position(1,1,z);
	mo->colour(col);

	mo->position(0,0,0);
	mo->colour(col);

	mo->position(1,-1,z);
	mo->colour(col);

	mo->position(0,0,0);
	mo->colour(col);

	mo->position(-1,-1,z);
	mo->colour(col);

	mo->position(0,0,0);
	mo->colour(col);

	mo->position(-1,1,z);
	mo->colour(col);

	//
	mo->position(1,1,z);
	mo->colour(col);

	mo->position(1,-1,z);
	mo->colour(col);

	mo->position(1,1,z);
	mo->colour(col);

	mo->position(-1,1,z);
	mo->colour(col);

	mo->position(-1,1,z);
	mo->colour(col);

	mo->position(-1,-1,z);
	mo->colour(col);

	mo->position(1,-1,z);
	mo->colour(col);

	mo->position(-1,-1,z);
	mo->colour(col);

	//
	mo->position(0,0,0);
	mo->colour(col);
	mo->position(n);
	mo->colour(col);

//	mo->end();

//	mo->begin(getName() + "_Material", Ogre::RenderOperation::OT_TRIANGLE_LIST);

	mo->position(-.2,1,z);
	mo->colour(col);

	mo->position(0,1.2,z);
	mo->colour(col);

	mo->position(0,1.2,z);
	mo->colour(col);

	mo->position(.2,1,z);
	mo->colour(col);

	mo->end();

	mDebugNode->attachObject(mo);

	mDebugNode->setVisible(true);

}


bool ChaseCamera::frameRenderingQueued(const FrameEvent& evt)
{
	return true;
}

bool ChaseCamera::frameStarted(const FrameEvent& evt)
{
	if (numAttachedObjects()==0) return true;
	if (target==NULL) return true;

//	Real max = 25.0f;
//	Real min = 15.0f;

	Real H = 7.0f;
	Vector3 distance = target->getPosition() + H - getPosition();
//	Real d = distance.normalise();

//	Real k = 2.0f;
	//mRotateSpeed = k*mMoveSpeed*d/max;
	mRotateSpeed = 2.5f;
	Real dt = evt.timeSinceLastFrame;

	Vector3 dr = target->getPosition() - getPosition();

	mAccel = Vector3::ZERO;
	mAccel = 10*dr*dt*dt;
	mVelocity = exp(1)*dr*dt + mAccel;

	Ogre::Real tooSmall = std::numeric_limits<Ogre::Real>::epsilon();

	// keep camera velocity below top speed and above epsilon
	if (mVelocity.squaredLength() > mTopSpeed * mTopSpeed)
		{
			mVelocity.normalise();
			mVelocity *= mTopSpeed;
		}
	else if (mVelocity.squaredLength() < tooSmall * tooSmall)
		mVelocity = Ogre::Vector3::ZERO;



//translate(dr*mMoveSpeed*(exp(dt) - exp(-dt)));
	translate(mVelocity);
	//setPosition(p);

	if (!useSpring) return true;

	Vector3 u = getOrientation() * Vector3::NEGATIVE_UNIT_Z;
	Vector3 v = target->getOrientation() * Vector3::NEGATIVE_UNIT_Z;


	Quaternion q = u.getRotationTo(v, u);
	q = Quaternion::Slerp(mRotateSpeed*dt, Quaternion::IDENTITY, q);
//        if(useSpring) rotate(q);
	if (useSpring) rotate(q, TS_WORLD);

	// fix up
	u = getOrientation() * Vector3::UNIT_Y;
	q = u.getRotationTo(upVector, u);
	q = Quaternion::Slerp(mRotateSpeed*dt, Quaternion::IDENTITY, q);
	rotate(q, TS_WORLD);

	return true;
}

bool ChaseCamera::frameEnded(const FrameEvent& evt)
{
	return true;
}

void FlyCamera::onKeyDown(const OIS::KeyEvent &e)
{
	switch (e.key)
		{
		case OIS::KC_UP:
		case OIS::KC_W:
			mForceVector.z = -mMove;
			break;

		case OIS::KC_DOWN:
		case OIS::KC_S:
			mForceVector.z = mMove;
			break;

		case OIS::KC_LEFT:
		case OIS::KC_A:
			mForceVector.x = -mMove;
			break;

		case OIS::KC_RIGHT:
		case OIS::KC_D:
			mForceVector.x = mMove;
			break;

		case OIS::KC_PGDOWN:
		case OIS::KC_E:
			mForceVector.y = -mMove;
			break;

		case OIS::KC_PGUP:
		case OIS::KC_Q:
			mForceVector.y = mMove;
			break;
		case OIS::KC_LSHIFT:
			break;
		default:
			break;
		}
}


void FlyCamera::onKeyUp(const OIS::KeyEvent &e)
{

	switch (e.key)
		{
		case OIS::KC_UP:
		case OIS::KC_W:
			mForceVector.z = 0;
			break;

		case OIS::KC_DOWN:
		case OIS::KC_S:
			mForceVector.z = 0;
			break;

		case OIS::KC_LEFT:
		case OIS::KC_A:
			mForceVector.x = 0;
			break;

		case OIS::KC_RIGHT:
		case OIS::KC_D:
			mForceVector.x = 0;
			break;

		case OIS::KC_PGDOWN:
		case OIS::KC_E:
			mForceVector.y = 0;
			break;

		case OIS::KC_PGUP:
		case OIS::KC_Q:
			mForceVector.y = 0;
			break;

		default:
			break;
		}
}


bool FlyCamera::frameEnded(const FrameEvent& evt)
{
	return true;
}

bool FlyCamera::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
	if (mCamera==NULL)
		{
//			overlay->hide();
			return true;
		}

//	overlay->show();

	if (mForceVector == Ogre::Vector3::ZERO)
		{
			mCurrentSpeed -= evt.timeSinceLastFrame*evt.timeSinceLastFrame*exp(2);
		}
	else
		{
			mCurrentSpeed += evt.timeSinceLastFrame;

		}

	OIS::Keyboard* keyb = InputManager::getSingletonPtr()->getKeyboard();
	slowMove = keyb->isModifierDown(OIS::Keyboard::Shift);

	mCurrentSpeed  = Math::Clamp(mCurrentSpeed,0.f, 1.f);

	mAccTransVector +=  mForceVector;
	mAccTransVector *= mCurrentSpeed;


	Quaternion q = mCamera->getOrientation();

	translate(q * mAccTransVector * (slowMove ? .1f : 1), Node::TS_LOCAL);

	mCamera->setPosition(Vector3::ZERO);

	if (lockLookAt)
		mCamera->lookAt(target->getPosition());

//	if (lockUpAxis) {
//			Vector3 v = getOrientation() * Vector3::UNIT_Y;
//			Quaternion q = v.getRotationTo(Vector3::UNIT_Y);
//			q = Quaternion::Slerp(exp(2)*evt.timeSinceLastFrame, Quaternion::IDENTITY, q);
//			rotate(q, TS_WORLD);
//		}

	mCamera->setFixedYawAxis(lockUpAxis);

	return true;
}


bool FlyCamera::frameStarted(const FrameEvent& evt)
{
	if(numAttachedObjects()==0) return true;

	Quaternion q = getOrientation();
	Vector3 p = getPosition();


	if(mCamera!=0)
		{
			p = mCamera->getDerivedPosition();
			q = mCamera->getDerivedOrientation();
		}

	if(mDebugNode)
		{
			mDebugNode->setVisible(true);

			mDebugNode->setPosition(p);
			mDebugNode->setOrientation(q);
		}

	return true;
}

void FlyCamera::onMouseMove(const OIS::MouseEvent& e)
{
	if (!freeLook || getAttachedObject (0)==NULL) return;

	const OIS::MouseState &ms = e.state;

	Quaternion q = mCamera->getOrientation();
	translate(q * Vector3::NEGATIVE_UNIT_Z*0.03*ms.Z.rel, Node::TS_LOCAL);

	if(useMouseButton && !ms.buttonDown(OIS::MB_Left)) return;

	Degree mRotX = Degree(-ms.X.rel * 0.13);
	Degree mRotY = Degree(-ms.Y.rel * 0.13);

	mCamera->yaw(mRotX);
	mCamera->pitch(mRotY);
}

void OrbitCamera::onMousePress(const OIS::MouseEvent& e)
{
	const OIS::MouseState &ms = e.state;

	Real x = ms.X.abs / (ms.width/2.f) - 1.f;
	Real y = 1.f - ms.Y.abs / (ms.height/2.f);
	Real z = 1 - x*x - y*y;
	z = z > 0 ? Math::Sqrt(z) : 0;

	mDeltaMouse = Vector3(x, y, z);
	mDeltaMouse.normalise();

	mLastMouse = mDeltaMouse;
}

void OrbitCamera::onMouseRelease(const OIS::MouseEvent& e)
{
//	mLastMouse = mDeltaMouse;
}

void OrbitCamera::onMouseMove(const OIS::MouseEvent& e)
{
	assert(mCamera!=0);
	assert(target!=0);

	update();

	OIS::Keyboard* keyb = InputManager::getSingletonPtr()->getKeyboard();
	const OIS::MouseState &ms = e.state;

	Real d = (target->getPosition() - mCamera->getDerivedPosition()).length();
	if(target->numAttachedObjects()!=0)
		d += target->getAttachedObject(0)->getBoundingRadius();
	d *= Math::Exp(-2);

	if(ms.Z.rel!=0)
		{
			mMove = false;

			// Zoom Ortho views
			if(mCamera->getProjectionType()==PT_ORTHOGRAPHIC)
				{
					mOrthoZoom -= .0001*Math::Exp(1)*ms.Z.rel;
					mOrthoZoom = fmax(0.00001, mOrthoZoom);

					int w = mCamera->getViewport()->getActualWidth();
					int h = mCamera->getViewport()->getActualHeight();
					mCamera->setOrthoWindow(w*mOrthoZoom, h*mOrthoZoom);
				}
			else
				{
//				mCamera->moveRelative(0.01*d*Vector3::UNIT_Z*-ms.Z.rel);
					Real dt = App::getSingletonPtr()->getFrameTime() * .001f;
					mCamera->moveRelative(dt*d*Vector3::NEGATIVE_UNIT_Z * ms.Z.rel);
				}
		}

	if (keyb->isModifierDown(OIS::Keyboard::Shift) && e.state.buttonDown(OIS::MB_Left))
		{
			mMove = false;
			mCamera->moveRelative(.33f*Vector3(-ms.X.rel, ms.Y.rel, 0));
		}

	if(mUseMiddle && !e.state.buttonDown(OIS::MB_Middle))
		{
			if (
			    (mUseKey && !keyb->isModifierDown(OIS::Keyboard::Alt)) ||
			    (mUsePress && !e.state.buttonDown(OIS::MB_Left))
			) return;
		}
	else if(!e.state.buttonDown(OIS::MB_Middle)) return;

	mMove = false;

	mLastMouse = mDeltaMouse;

	Real x = ms.X.abs / (ms.width/2.f) - 1.f;
	Real y = 1.f - ms.Y.abs / (ms.height/2.f);
	Real z = 1 - x*x - y*y;
	z = z > 0 ? Math::Sqrt(z) : 0;

	Degree mRotX = Degree(-ms.X.rel * 0.33);
	Degree mRotY = Degree(-ms.Y.rel * 0.33);

	mDeltaMouse = Vector3(x, y, z);
	mDeltaMouse.normalise();

	mCamera->setOrientation(Quaternion::IDENTITY);

	Vector3 axis = mDeltaMouse.crossProduct(mLastMouse);
	Real theta = mDeltaMouse.dotProduct(mLastMouse);

	Quaternion q;
	q.FromAngleAxis(Radian(theta), axis);
	q.normalise();
	q = getOrientation() * q;
	q.normalise();
	setOrientation(q);
}

bool OrbitCamera::frameStarted(const FrameEvent& evt)
{
	Quaternion q = getOrientation();
	Vector3 p = getPosition();
//	if(p.isNaN()) return true;

//	if(mDebugNode!=0)
//		mDebugNode->setVisible(true);

	if(mCamera)
		{
			p = mCamera->getDerivedPosition();
			q = mCamera->getDerivedOrientation();

			Vector3 v = mTargetPos - mCamera->getPosition();

			Ogre::Real tooSmall = std::numeric_limits<Ogre::Real>::epsilon();
			if(v.length() < tooSmall) mMove = false;
			
			Vector3 u =  v * Math::Exp(.5+10*evt.timeSinceLastFrame) * evt.timeSinceLastFrame;

			const AxisAlignedBox& aabb = target->_getWorldAABB();
			if(aabb.contains(p+u)) mMove = false;

			if(mMove)
				mCamera->moveRelative(u);

		}

	if(mDebugNode)
		{
			mDebugNode->setPosition(p);
			mDebugNode->setOrientation(q);
		}

	return true;
}


bool OrbitCamera::frameEnded(const FrameEvent& evt)
{
//	if (mUseKey)
//		{
//			OIS::Keyboard* keyb = InputManager::getSingletonPtr()->getKeyboard();
//			if (!keyb->isModifierDown(OIS::Keyboard::Alt)) return true;
//		}

//	update();

//	if (target==NULL) return true;
//	setPosition(target->getPosition());

//	if (numAttachedObjects()==0) return true;
//	if (getAttachedObject(0)==NULL) return true;
//	Camera* cam = dynamic_cast<Ogre::Camera*>(getAttachedObject(0));


	return true;
}

void OrbitCamera::update()
{
	if (getCamera()==NULL || target==NULL) return;
	if(target==mSceneMgr->getRootSceneNode()) return;

	Vector3 v = getCamera()->getDerivedPosition();
	Vector3 oldPos = getPosition();

//	if(target->getPosition().isNaN()) return;

	setPosition(target->getPosition());
	v -= getCamera()->getDerivedPosition();

	Quaternion q = getOrientation();
	q = q.Inverse();
	getCamera()->moveRelative(q*v);
}
